'''
Created on 10.03.2012

@author: myaut
'''

from xml.etree.ElementTree import ElementTree, Element, parse
from collections import OrderedDict

class ASTNode:
    filter = lambda node, ntag: True
    
    def __init__(self, filter = None):
        self.children = OrderedDict()
        
        self.height = 0
        self.width = 0  
        
        if filter: 
            self.filter = filter
    
    def __eq__(self, other):
        return self.id == other.id
    
    def __str__(self):
        s = self.name
        
        s += ' ' + ','.join(self.attrs.values())
        
        return s
    
    def before_children(self, context):
        # Called on creation
        pass
    
    def after_children(self, context):
        pass
    
    def after_child(self, ctag, cnode, context):
        pass
    
    def create(self, enode, cid = 0, context = None):
        self.name = enode.tag
        self.id = enode.attrib['id']
        self.tag = str(enode.attrib.get('tag', cid))
        
        black_attr = ('id', 'tag', 'line')
        self.attrs = dict(filter(lambda a: a[0] not in black_attr, 
                                 enode.attrib.items()))
        
        if not ASTNode.filter(self, self.tag):
            return self.tag
        
        cid = 0
        
        # print '%s:%s -> %s created [%s]' % (self.name, self.id, self.tag, self.attrs)
        
        self.before_children(context)
        
        for echild in list(enode):
            cnode = self.__class__()
            ctag = cnode.create(echild, cid, context)
            
            self.children[ctag] = cnode
            
            self.after_child(ctag, cnode, context)
            
            cid += 1
        
        self.after_children(context)
        
        return self.tag
    
    def __getitem__(self, i):
        if i in self.children:
            return self.children[i]
        
        return self.attrs[i]
    
    def __iter__(self):
        return iter(self.children.values())

class AST:    
    def __init__(self, lang = None):
        self.lang = lang
        self.nodeid = 0
        
        self.root = None
        self.etree = None
    
    def read(self, f):
        self.etree = parse(f)
        self.root = self.etree.getroot()
        self.ast_fn = self.root.attrib.get('ast_fn', '?')
    
    def from_c_ast(self, ast_fn, ast_root):
        'Imports data from pycparser.c_ast'
        self.ast_fn = ast_fn
        self.root = Element("AST", {'lang': self.lang, 'arc_fn': ast_fn})
        self.etree = ElementTree(self.root)
        
        def gen_attrs(ast_node, node_tag = None):
            attrs = {}
            
            self.nodeid += 1
            attrs['id'] = str(self.nodeid)
            
            if node_tag:
                attrs['tag'] = '#' + node_tag
            
            if ast_node.coord:
                attrs['line'] = str(ast_node.coord.line)
            
            for aname in ast_node.attr_names:
                avalue = getattr(ast_node, aname)
                
                if avalue:
                    if isinstance(avalue, list):
                        attrs[aname] = ",".join(str(a) for a in avalue)
                    else:
                        attrs[aname] = str(avalue)
                        
            return attrs
        
        def find_node_tag(parent, child):
            for aname in dir(parent):
                if not aname.startswith('_'):
                    if getattr(parent, aname) == child:
                        return aname
        
        def append_to_tree(parent, ast_node, nodeid = None):
            tag = ast_node.__class__.__name__
            attrs = gen_attrs(ast_node, nodeid)
            
            el = Element(tag, attrs)
            
            # print 'Added %s %s' % (tag, attrs)
            
            for child in ast_node.children():
                # Problem is that c_ast.Node.children does'nt provide names,
                # i.e. lvalue and rvalue for Assignment may be determined only by order of
                # subelements. Reverse searching over Node.__dict__ solves this
                child_node_tag = find_node_tag(ast_node, child)
                
                append_to_tree(el, child, child_node_tag)
            
            parent.append(el)
        
        append_to_tree(self.root, ast_root)
        
    def write(self, f):
        self.etree.write(f)
    
    def get_ast_root(self):
        return self.root.find('FileAST')
    
    def get_node_id(self, ast_node):
        return self.ast_fn + '@' + ast_node.attrib.get('id', '0')
    
    def build_tree(self, root = ASTNode(), context = None):
        root.create(self.get_ast_root(), context = context)
        
        return root